iT邦幫忙

2022 iThome 鐵人賽

DAY 20
1

Day20 自己做一個價值幾十萬的動態網站

第二十課:useFetch的loading特效應用

前一天我們都順利地使用了useFetch來抓取我們要的資料,提到了在useFetch中useEffect的作用,而今天要來處理如何加入loading時的特效,讓抓取資料的過程中,提升我們的使用者體驗。

應用useFetch的loading與error 搭配skeleton loading

這邊將實作如何將我們的首頁UI加入與Api串接時的特效,屬於實物面的功能特效,與一開始做的UI特效都不同,因為有牽扯到Api。

skeleton loading介紹與來源

skeleton 為骨架之意,代表說是載入時先顯現到時候資料會出來的樣子,以心理學來說,使用者會覺得好像快出現了,至少已經一半了可以再等等,來將低因網速使顧客跳出網站的流失率,且在功能上也會讓連線更快,因為猜分需要抓資料的部分與UI生成時間,一般的網站都會先抓取好全部再一起顯示畫面,相較於這樣傳統的網站,使用await axios搭配動畫會讓人有這個網站載入更快的錯覺,因抓取龐大資料所需時間最久,了解後這邊我們會將原本的"人氣民宿.."先做改變。


所以首先我們要以usefetch中的loading為主,當他傳入loading值為true,代表在載入中,我們就可以放上skeleton loading特效。所以回到我們的popularHotel的父層feature來應用到我們沒應用到的loading useState。


沒加入skeleton loading的版本

const PopularHotels = ({ dataArray, loading }) => {
//loading 現在從 feature useFetch Api那傳資料回來了
return (
<div className='popularHotels'>
    { loading ?  <>我在載入不顯示</>  :
//利用loading = true 時 顯示 "冒號" 後面的原本dataArray 
//利用loading = false 時 顯示 "問號" 後面的<>我在載入不顯示</> 
     <>{dataArray.map((item, index) =>
        <Link to={`/hotels/${item._id}`} style={{ textDecoration: "none", color: "inherit" }}  key={index}>
            <div className="item" >
                <img src={item.photos[0]} alt="" />
                <div className="itemInfo">
                    <div className="title">
                        {item.name}
                    </div>
                    <div className="subTitle">
                        {item.city}
                    </div>
                    <div className="price">
                        TWD {item.cheapestPrice.toLocaleString()} 起
                    </div>
                    <div className="rate">
                        <button>{item.rating}</button>
                        <span>{item.rating >= 9.5 ? "好極了" : "傑出"}</span>
                        <p>{item.comments.toLocaleString()}則評論</p>
                    </div>
                </div>
            </div>
        </Link>)}
    </>
    }
</div>
)
}



並來製作Skeleton Component細節


這邊其實兩種方法都可以看使用者覺得哪個管理上會比較方便,到時後可以一起決定要導入的props與統一導入性,不再增加更多的遮罩components的另一種可讀性,可以選擇這樣輸出

const Skeleton = ({ type,length}) => {
const number = length
//length為傳入資料數目,讓這個遮罩要計算要生成多少個遮障
     const PopularHotelSkeleton = ({i}) => (
         //多一個{i}是因為解決list需要index的部分,不然會有warning
        <div className="popularHotelSK" key={i} >
            <div className='imgSK' />
            <div className="InfoSK">
                <div className="titleSK" />
                <div className="subTitleSK" />
                <div className="priceSK" />
                <div className="rateAndCommentSK" />
            </div>
        </div>
    );
    const AmountSkeleton = () => (
        <div className="amountSK" />
    );

    if (type === "popularHotel") return Array(number).fill().map((item,i)=><PopularHotelSkeleton key={i}/>);
//用Array來排列這麼多的遮罩,假設說我們傳入為7,那他就會生成7個遮罩,取代原本的資料列 .map() 來把array列陣同時又加index不然又會有error發生
    if (type === "Amount") return (<AmountSkeleton />);
    //amount 不用是因為他有上面得data.js的UI內資料不像PopularHotels是整個傳過來的資料總數都不確定
    //而Amount是因為是我們的測試做的type與city就都知道有這麼多就不用在傳入length告訴他要生成多少個遮罩
}
export default Skeleton

並這邊下面我們加入type子句,到時候我們要去popularHotels啟動遮罩時,告訴他說我要用的skeleton的type是popularHotel,這邊就會輸出我們的<PopularHotelSkeleton />並這邊我們會加入想要生成的遮罩數目為lengh

.popularHotelSK {
    position: relative;
    .imgSK {
        width: 250px;
        height: 230px; //跟原本img一樣的長寬高
        background-color:#ECECEC;
        margin-bottom: 2px;
    }
    .InfoSK {
        display: flex;
        flex-direction: column;
        gap:2px;
        .titleSK {
        width: 220px;
        height: 25px; 
        background-color:#ECECEC;
        }
        .subTitleSK {
        width: 70px;
        height: 15px; 
        background-color:#ECECEC;
        }
        .priceSK {
            width: 150px;
            height: 20px; 
            background-color:#ECECEC;

        }
        .rateAndCommentSK {
            width: 190px;
            height: 25px; 
            background-color:#ECECEC;
        }
    }
}
.amountSK {
    position: relative;
    width: 150px;
    height: 30px; 
    background-color:#ECECEC;
}

並做好後,我們先回到,我們的popularHotel.js component在那導入完整的Sketelon包括import 還有我們上面要判讀的type與length

並完成後應該就會有簡單,但沒有加入動畫的顯影。

@keyframes的應用與介紹


了解@keyframes後,我們將利用keyframes與動畫幀數的概念,配合scss加入在我們原本的Sketelon loading上,這邊有很多種動畫玩法,我們就先用最基本且較簡單的控制他的透明度來明暗、明暗的顯示載入畫面。

@keyframes skeleton {
    to {
      opacity: 0.6;
    }
  }
animation: skeleton 1s ease infinite alternate;

這邊完成後可以回popularHotels,把我們設的isloading假boolean值刪掉,串接回原來的loading他就能正常在axios.get時,抓取資料等待時間時會顯示。

將所有前面有資料傳接的useFetch都換入skeleton loading特效

最後來收尾一下將我們的前面只要有資料傳接的部分,資料需要載入的,都搭配useFetch中useState的loading,結合我們的skeleton loading來做特效,並我們在feature.jsx component使用的useFetch在導入在popularHotels,而我們接下來要加的AmountSkeleton會加在我們的categories.jsx,來遮罩我們的type與city統計的數目,所以要用到categories裡面的useFetch,使用他的loading並一樣導入skeleton並props我們的AmountSkeleton來讓他識別,資料長度length就不用因為我們直接導入在map內,如下。

<div className="desc">
 {loading ? <Skeleton type="Amount"/> : `${data[index]}間住宿`}
</div>

最後完成應該會示範影片應該會是下面這樣,全部都在載入的地方都是有關資料庫的連接。

github Day20的連結

結論

恭喜目前鐵人賽已經完成到2/3,這幾天我們會發現網站的許多細節,現今看似理所當然卻富含很多巧思,甚至涉及到行銷的概念,如何用你的產品來留住消費者,讓他們能使用起來最舒服且安心。我們在剩下的10天內,雖然我們無法把所有細節的完成,但秉持著這樣的細節與現在開發慢慢開始所累積的概念,不管之後去跟別人合作,還是請別人幫你製作都會有概念,對自己都是無往不利的。


上一篇
「全端挑戰」 req.query與Array的查詢實作,全端串接新Api、練習props資料傳輸關係
下一篇
「全端挑戰」認識Context Api並完成註冊、登入創建會員制
系列文
自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言